#include <fstream>
#include <algorithm>
#include <cstdlib>
#include <conio.h>
#include "library.h"
#include "utility.h"

using namespace std;

Book::Book(const string& szerzo, const string& cim, const string& kiado, int ev, int kiadas, const string& isbn):
	author(szerzo),title(cim),publisher(kiado),year(ev),issue(kiadas),ISBN(isbn),available(true),at(0){}

void Book::write(ostream& out=cout)const{
	if(id=="0") return;
	if(out==cout){
		cout << "Szerzo: " << author << endl;
		cout << "Cim: " << title << endl;
		cout << "Kiado: " << publisher << endl;
		cout << "Kiadas eve: " << year << endl;
		cout << issue << ". kiadas" << endl;
		cout << "ISBN-szam: " << ISBN << endl;
		cout << (available?"Konyvtarban":"Kikolcsonozve "+at->getID()+"-s tagnak, hatarido: "+datecache) << endl;
		cout << "Azonosito: " << id << endl << endl;
		
	}
	else{
		out << ISBN << endl;
		out << author << endl;
		out << title << endl;
		out << publisher << endl;
		out << to_string(year) << endl;
		out << to_string(issue) << endl;
		out << (available?"0":to_string(limit)) << endl;
		out << (available?"0":at->getID()) << endl;
		out << id << endl;
	}
}

bool Book::LendAt(const Member* m, time_t lim){
	if(!available) return false;
	available = false;
	limit = lim;
	char s[60];
	strftime(s,60,"%Y-%m-%d %H:%M",localtime(&limit));
	datecache = string(s);
	at = m;
	return true;
}

bool Book::Back(const Member* m, bool NoMessage = false){
	if(at!=m) return false;
	if(!NoMessage){
		if(time(0)>limit){
			cout << "A konyv visszahozasa kesett, hatarido: " << datecache << endl;
		}
		else{
			cout << "A konyv hataridon belul visszakerult." << endl;
		}
	}
	available = true;
	at = nullptr;
	return true;
}

string Book::getData(ConditionType ct)const{
	switch(ct){
		case Author: return Input::tolower(author);
		case Title: return Input::tolower(title);
		case Isbn: return Input::tolower(ISBN);
		default: throw Impossible();
	}
}

Member::Member(const string& nev, const string& cim, const string& eler, const string& azon=""):
	name(nev),address(cim),mail(eler),id(azon){}

Member::~Member(){}

void Member::writeBooks()const{
	cout << "A nala levo konyvek: " << endl;
	for(auto it=books.begin();it!=books.end();++it){
		if((*it)->getID()=="0") continue;
		(*it)->write();
	}
}

void Member::Back(const Book* b){
	for(auto it=books.begin();it!=books.end();++it){
		if(*it == b){
			*it = books.back();
			books.pop_back();
			return;
		}
	}
}

string Member::getData(ConditionType ct)const{
	switch(ct){
		case Name: return Input::tolower(name); break;
		case Address: return Input::tolower(address); break;
		case MemberType: return Input::tolower(getType()); break;
		default: throw Impossible();
	}
}

void Member::setData(int field, const std::string& val){
	switch(field){
		case 1: name=val; break;
		case 2: address=val; break;
		case 3: mail=val; break;
		default: throw Impossible();
	}
}

void Member::ClearBooks(){
	if(books.size()){
		cout << "A tagnal meg konyvek vannak." << endl;
		char ch;
		do{
			cout << "Ezen konyveket visszahozzuk, vagy toroljuk az adatbazisbol? (v/t)";
			cin >> ch;
		}while(ch!='v' && ch!='t');
		if(ch=='v'){
			for(auto it = books.begin(); it!=books.end(); ++it){
				(*it)->Back(this);
			}
		}
		else{
			for(auto it = books.begin(); it!=books.end(); ++it){
				(*it)->setID("0");
			}
		}
	}
	
}

bool Student::LendTo(Book* b){
	if(books.size()>=5) return false;
	books.push_back(b);
	return true;
}
bool Professor::LendTo(Book* b){
	books.push_back(b);
	return true;
}
bool Student2::LendTo(Book* b){
	if(books.size()>=4) return false;
	books.push_back(b);
	return true;
}
bool Other::LendTo(Book* b){
	if(books.size()>=2) return false;
	books.push_back(b);
	return true;
}

void Member::write(ostream& out)const{
	if(out==cout){
		cout << "Nev: " << name << endl;
		cout << "Cim: " << address << endl;
		cout << "Elerhetoseg: " << mail << endl;
		cout << "Azonosito: " << id << endl << endl;
	}
	else{
		out << id << endl;
		out << Crypt::encrypt(name) << endl;
		out << Crypt::encrypt(address) << endl;
		out << Crypt::encrypt(mail) << endl;
	}
}

void Student::write(ostream& out=cout)const{
	if(out==cout){
		cout << "Hallgato" << endl;
		Member::write(out);
	}
	else{
		Member::write(out);
	}
}
void Professor::write(ostream& out=cout)const{
	if(out==cout){
		cout << "Oktato" << endl;
		Member::write(out);
	}
	else{
		Member::write(out);
	}
}
void Student2::write(ostream& out=cout)const{
	if(out==cout){
		cout << "Mas egyetem hallgatoja" << endl;
		Member::write(out);
	}
	else{
		Member::write(out);
	}
}
void Other::write(ostream& out=cout)const{
	if(out==cout){
		cout << "Egyeb" << endl;
		Member::write(out);
	}
	else{
		Member::write(out);
	}
}

Librarian::Librarian(const std::string& a, const std::string& b):fnev(a),pwd(b){}

bool Librarian::Checkpwd(const string& fnev, const string& pwd){
	return (fnev==this->fnev) && (pwd==this->pwd);
}

void Librarian::write(ostream& out)const{
	out << Crypt::encrypt(fnev) << endl;
	out << Crypt::encrypt(pwd) << endl;
}


Library::Library(){
	LoadData();
	SignIn();
}

Library::~Library(){
	SaveData();
	for(auto it=members.begin();it!=members.end();++it){
		delete *it;
	}
	for(auto it=books.begin();it!=books.end();++it){
		delete *it;
	}
	for(auto it=librarians.begin();it!=librarians.end();++it){
		delete *it;
	}
}

void Library::NewMember(Member* m, int type){
	if(members.size()!=0){
		string last_id = members.back()->getID();
		last_id[0] = to_string(type)[0];
		string id = to_string(atoi(last_id.c_str())+1);
		if(atoi(id.c_str())%10000==0) throw OutOfIDs();
		m->setID(id);
		members.push_back(m);
	}
	else{
		string id="00000";
		id[0] = type+48;
		m->setID(id);
		members.push_back(m);
	}
}

void Library::NewBook(const Book& b, int n){
	int c=1;
	for(auto it=books.begin();it!=books.end();++it){
		if((*it)->getData(Isbn)==b.getData(Isbn)){
			(*it)->setID(b.getData(Isbn)+"-"+to_string(c));
			++c;
		}
	}
	for(int i=0; i<n; ++i){
		books.push_back(new Book(b));
		books.back()->setID(b.getData(Isbn)+"-"+to_string(c+i));
	}
}

void Library::DeleteMember(const string& id){
	for(auto it=members.begin();it!=members.end();++it){
		if((*it)->getID()==id){
			Member* p = *it;
			(*it)=members.back();
			members.pop_back();
			p->ClearBooks();
			return;
		}
	}
	throw NoSuchMember();
}

void Library::DeleteBook(const string& id){
	string isbn;
	bool found=false;
	for(auto it=books.begin();it!=books.end();++it){
		if((*it)->getID()==id){
			Book* p = *it;
			isbn = (*it)->getData(Isbn);
			(*it)=books.back();
			books.pop_back();
			p->setID("0");
			found=true;
			break;
		}
	}
	if(!found) throw NoSuchBook();
	int c=1;
	for(auto it=books.begin();it!=books.end();++it){
		if((*it)->getData(Isbn)==isbn){
			(*it)->setID(isbn+"-"+to_string(c));
			++c;
		}
	}
}

void Library::ListMembers(ConditionType ct, const string& s)const{
	string z = Input::tolower(s);
	for(auto it=members.begin();it!=members.end();++it){
		if(ct==All || (*it)->getData(ct).find(z)!=string::npos){
			(*it)->write();
		}
	}
}

void Library::ListBooks(ConditionType ct, const string& s)const{
	string z = Input::tolower(s);
	for(auto it=books.begin();it!=books.end();++it){
		if(ct==All || (*it)->getData(ct).find(z)!=string::npos){
			(*it)->write();
		}
	}
}

void Library::ModifyMember(const string& id, int field, const string& val){
	for(auto it=members.begin();it!=members.end();++it){
		if((*it)->getID()==id){
			(*it)->setData(field,val);
			return;
		}
	}
	throw NoSuchMember();
}

void Library::ViewMember(const string& s)const{
	for(auto it=members.cbegin();it!=members.cend();++it){
		if((*it)->getID()==s){
			(*it)->write();
			(*it)->writeBooks();
			return;
		}
	}
	throw NoSuchMember();
}

void Library::Lend(const std::string& id,const std::string& isbn){
	Member* m = nullptr;
	for(auto it=members.begin();it!=members.end();++it){
		if((*it)->getID()==id){
			m = *it;
			break;
		}
	}
	if(m==nullptr) throw NoSuchMember();
	Book* b = nullptr;
	for(auto it=books.begin();it!=books.end();++it){
		if((*it)->getID().find(isbn)==0 && (*it)->LendAt(m,m->limit())){
			b = *it;
			break;
		}
	}
	if(b==nullptr) throw NoSuchBook();
	if(!m->LendTo(b)){
		b->Back(m,true);
		throw LendError();
	}
}

void Library::Back(const std::string& id,const std::string& isbn){
	Member* m;
	for(auto it=members.begin();it!=members.end();++it){
		if((*it)->getID()==id){
			m = *it;
			break;
		}
	}
	if(m==nullptr) throw NoSuchMember();
	Book* b;
	for(auto it=books.begin();it!=books.end();++it){
		if((*it)->getData(Isbn)==isbn && ((*it)->Back(m))){
			b = *it;
			break;
		}
	}
	if(b==nullptr) throw NoSuchBook();
	m->Back(b);
}

void Library::LoadData(){
	ifstream f1("tagok.dat");
	if(f1.fail()) throw FileError();
	string s,z;
	getline(f1,s);
	while(s!="#####"){
		s=Crypt::decrypt(s);
		getline(f1,z);
		z=Crypt::decrypt(z);
		librarians.push_back(new Librarian(s,z));
		if(f1.eof()) throw FileError();
		getline(f1,s);
	}
	string a,b,c,d,e,f,g,h,i;
	getline(f1,a);
	while(!f1.fail()){
		getline(f1,b);
		b=Crypt::decrypt(b);
		getline(f1,c);
		c=Crypt::decrypt(c);
		getline(f1,d);
		d=Crypt::decrypt(d);
		switch(a[0]){
			case '1': members.push_back(new Student(b,c,d,a)); break;
			case '2': members.push_back(new Professor(b,c,d,a)); break;
			case '3': members.push_back(new Student2(b,c,d,a)); break;
			case '4': members.push_back(new Other(b,c,d,a)); break;
		}
		getline(f1,a);
	}
	f1.close();
	ifstream f2("konyvek.dat");
	if(f2.fail()) throw FileError();
	getline(f2,a);
	getline(f2,b);
	while(!f2.fail()){
		getline(f2,c);
		getline(f2,d);
		getline(f2,e);
		getline(f2,f);
		getline(f2,g);
		getline(f2,h);
		getline(f2,i);
		books.push_back(new Book(b,c,d,atoi(e.c_str()),atoi(f.c_str()),a));
		if(g!="0"){
			Member* m = nullptr;
			for(auto it=members.begin();it!=members.end();++it){
				if((*it)->getID()==h){
					m = *it;
					break;
				}
			}
			m->LendTo(books.back());
			books.back()->LendAt(m,atoi(g.c_str()));
		}
		books.back()->setID(i);
		getline(f2,a);
		getline(f2,b);
	}
}

void Library::SaveData(){
	ofstream f1("tagok.dat");
	for(auto it=librarians.begin();it!=librarians.end();++it){
		(*it)->write(f1);
	}
	f1 << "#####" << endl;
	for(auto it=members.begin();it!=members.end();++it){
		(*it)->write(f1);
	}
	f1.close();
	ofstream f2("konyvek.dat");
	for(auto it=books.begin();it!=books.end();++it){
		(*it)->write(f2);
	}
	f2.close();
}

void Library::SignIn(){
	string fn,p;
	while(true){
		p="";
		cout << "Kerem adja meg a felhasznalonevet! ";
		cin >> fn;
		cout << "Kerem adja meg a jelszot! ";
		char ch;
		ch=_getch();
		while(ch!=13){
			if(ch!=8)
				p.push_back(ch);
			else if(p.length()>0)
				p.pop_back();
			ch = _getch();
		}
		for(int i=0;i<(int)librarians.size();++i){
			if(librarians[i]->Checkpwd(fn,p)){
				userID=i;
				return;
			}
		}
		cout << endl << "Nem sikerult a bejelentkezes, kerem probalja ujra!" << endl;
	}
}

void Library::Logout(){
	userID=-1;
	SignIn();
}
